import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { Bars, HBars } from '@/components/charts/charts'; import { LaunchesTable } from '@/components/launches/launches-table'; import { SitesMap } from '@/components/launches/sites-map'; import { Block, Empty, Head } from '@/components/satellite/primitives'; import { Container, Stat } from '@/components/ui/section'; import { Unavailable } from '@/components/ui/unavailable'; import { api, ApiError, safe } from '@/lib/api'; import { fmtDate, fmtInt, num } from '@/lib/format'; import { routes, SITE_URL } from '@/lib/site'; type Params = { params: Promise<{ slug: string }> }; type SiteDetail = Awaited>['data']; async function load(slug: string): Promise { try { return (await api.launchSite(slug)).data; } catch (e) { if (e instanceof ApiError && e.notFound) notFound(); throw e; } } /** The detail endpoint has no totals; derive them from the per-year series (real data, no guesses). */ function totals(s: SiteDetail) { const launches = s.years.reduce((a, y) => a + (num(y.launches) ?? 0), 0); const payloads = s.years.reduce((a, y) => a + (num(y.payloads) ?? 0), 0); const first = s.years[0]?.year ?? null; const last = s.years[s.years.length - 1]?.year ?? null; const lastLaunch = s.recent_launches.reduce((m, l) => (l.launch_date && (!m || l.launch_date > m) ? l.launch_date : m), null); return { launches, payloads, first, last, lastLaunch }; } export async function generateMetadata({ params }: Params): Promise { const { slug } = await params; const res = await safe(api.launchSite(slug)); if (!res) return { title: 'Launch site', robots: { index: false } }; const s = res.data; const t = totals(s); const title = `${s.name} — Launch site${s.country_name ? `, ${s.country_name}` : ''}`; const description = `${s.name}${s.country_name ? ` (${s.country_name})` : ''}: ${fmtInt(t.launches)} orbital launches and ${fmtInt(t.payloads)} payloads${t.first ? ` since ${t.first}` : ''}${t.lastLaunch ? `, most recent on ${fmtDate(t.lastLaunch)}` : ''}. Launches per year, recent launches and top owners on SatelliteIndex.`; const canonical = routes.launchSite(s.slug); return { title, description, alternates: { canonical }, openGraph: { title, description, url: `${SITE_URL}${canonical}` }, twitter: { card: 'summary', title, description } }; } export default async function LaunchSitePage({ params }: Params) { const { slug } = await params; const s = await load(slug); const t = totals(s); const hasCoords = s.latitude !== null && s.longitude !== null; const yearsData = s.years.map((y) => ({ x: String(y.year), y: num(y.launches) ?? 0 })); const owners = [...s.owners].sort((a, b) => (num(b.launches) ?? 0) - (num(a.launches) ?? 0)).slice(0, 12); const jsonLd = { '@context': 'https://schema.org', '@type': 'Place', name: s.name, url: `${SITE_URL}${routes.launchSite(s.slug)}`, identifier: s.code, ...(hasCoords ? { geo: { '@type': 'GeoCoordinates', latitude: s.latitude, longitude: s.longitude } } : {}), ...(s.country_name ? { address: { '@type': 'PostalAddress', addressCountry: s.country_code ?? s.country_name } } : {}) }; return (